<?php

require_once("evalable.php");

class ListObject extends Evalable {

    var $list = null;

    function ListObject($lst=null) {
        if ($lst)
            $this->list = $lst;
        else
            $this->list = array();
    }

    function lispeval($env, $args=null) {
        $fn = $this->list[0]->lispeval($env);
        $fn_class = get_class($fn);
        if (
                $fn_class == "LambdaObject" ||
                $fn_class == "MacroObject" ||
                $fn_class == "FunctionObject" ||
                $fn_class == "SyntaxObject"
        ) {
            return $fn->lispeval($env, array_slice($this->list, 1));
        } else {
            echo "Error!!! " . $fn->repr() . " is not applyable";
            exit();
        }
    }

    function count() {
        return count($this->list);
    }

    function repr() {
        if ($this->list == array()) {
            return "()";
        }
        if (is_object($this->list[0])) {
            $str = $this->list[0]->repr();
        } else {
            $str = $this->list[0];
        }
        $s = "(" . $str;
        foreach (array_slice($this->list, 1) as $e) {
            if (is_object($e))
                $str = $e->repr();
            else
                $str = $e;
            $s .= " " . $str;
        }
        return $s . ")";
    }

    function getitem($i) {
        return $this->list[$i];
    }

    function cons($item) {
        $n = new ListObject($this->list);
        array_unshift($n->list, $item);
        return $n;
    }

    function logic_and($other) {
        if (get_class($other) == "LogicObject") {
            if ($this->nullp()) {
                return new LogicObject(0 & $other->v);
            } else {
                return new LogicObject(1 & $other->v);
            }
        }
    }

    function first() {
        return $this->list[0];
    }

    function replaca($val) {
        $this->list[0] = $val;
        return $this;
    }

    function second() {
        if (isset($this->list[1])) {
            return $this->list[1];
        } else {
            return new ListObject(array());
        }
    }

    function third() {
        if (isset($this->list[2])) {
            return $this->list[2];
        } else {
            return new ListObject(array());
        }
    }

    function rest() {
        if (isset($this->list[1])) {
            return new ListObject(array_slice($this->list, 1));
        } else {
            return new ListObject(array());
        }
    }

    function nullp() {
        if (count($this->list) == 0) {
            return new LogicObject(1);
        } else {
            return new LogicObject(0);
        }
    }

    function iquote($env) {
        $answer = array();
        $length = count($this->list);
        $i = 0;
        while ($i < $length) {
            if (is_object($this->list[$i]) && get_class($this->list[$i]) == "ListObject") {
                $answer[] = $this->list[$i]->iquote($env);
            } elseif (is_string($this->list[$i])) {
                if ($this->list[$i] == ',') {
                    $i++;
                    if (is_string($this->list[$i])) {
                        if ($this->list[$i] == '@') {
                            $i++;
                            $answer = array_merge($answer, $this->list[$i]->lispeval($env)->list);
                        } else {
                            $answer[] = $this->list[$i]->lispeval($env);
                        }
                    } else {
                        $answer[] = $this->list[$i]->lispeval($env);
                    }
                }
            } else {
                $answer[] = $this->list[$i];
            }
            $i = $i + 1;
        }
        return new ListObject($answer);
    }

    function replacd($val) {
        $this->list = array_merge(array($this->list[0]), $val->list);
        return $this;
    }

    function append($lst) {
        if (get_class($lst) == "ListObject") {
            $this->list = array_merge($this->list, $lst->list);
        } else {
            $this->list = array_merge($this->list, $lst);
        }
    }

    function nconc($lst) {
        $this->append($lst);
        return $this;
    }

    function assoc($item) {
        foreach ($this->list as $pair) {
            if ((get_class($pair) == "PairObject") || (get_class($pair) == "ListObject")) {
                $key = $pair->first();
                if ((get_class($key) == "SymbolObject") && (get_class($item) == "SymbolObject")) {
                    if ($key->n == $item->n) {
                        return $pair->second();
                    }
                } else {
                    if ($key == $item) {
                        return $pair->second();
                    }
                }
            } else {
                echo "Error! assoc: second argument must be list of lists/pairs";
                exit();
            }
        }
        return new LogicObject(0);
    }

}